/*
 *  connection.cc
 *  wonderland-project
 *
 *  Created by Ben Nolan on 15/10/08.
 *  Copyright 2008 Nolan Consulting Limited. All rights reserved.
 *
 */

#include "connection.h"
#include "v8defines.h"
#include "scripting_context.h"
#include "xmpp_connection.h"

#include <boost/bind.hpp>
#include <boost/signals.hpp>
#include <iostream>

namespace JSConnection{
  
  v8::Handle<v8::ObjectTemplate> instance_template;
  v8::Handle<v8::FunctionTemplate> function_template;
  v8::Persistent<v8::Context> context_;

  void onConnect(v8::Persistent<v8::Function> func){
    context_->Enter();
    func->Call(context_->Global(), 0, NULL);
  }

  void onError(v8::Persistent<v8::Function> func, std::string msg){
    std::cout << "ERROR" << msg << std::endl;

    // todo - add msg to arguments
    context_->Enter();
    func->Call(context_->Global(), 0, NULL);
  }
  
  void onMessage(v8::Persistent<v8::Function> func, std::string sender, std::string msg){
    v8::Handle<v8::Value> args[] = { v8::String::New(sender.c_str()), v8::String::New(msg.c_str()) };
    
    context_->Enter();
    func->Call(context_->Global(), 2, args);
  }
  
  void onDisconnect(v8::Persistent<v8::Function> func){
    context_->Enter();
    func->Call(context_->Global(), 0, NULL);
  }  
  
  V8CALLBACK(Connection,_connectEvent){
    if(args.Length()!=2)
      return ThrowException(v8::String::New("Invalid argument"));

    if (args[1].IsEmpty() || !args[1]->IsFunction()){
      return ThrowException(v8::String::New("Invalid argument"));
    }

    v8::String::Utf8Value event(args[0]);
    v8::Persistent<v8::Function> func = v8::Persistent<v8::Function>::New(
      v8::Local<v8::Function>::Cast(args[1])
    );

    if(*event == (std::string) "connect"){
      V8INTERNAL(Wonderland::XmppConnection*)->signal_connected.connect(
        boost::bind(&onConnect, func)
      );
    }else if(*event == (std::string) "error"){
      V8INTERNAL(Wonderland::XmppConnection*)->signal_error.connect(
        boost::bind(&onError, func, _1)
      );
    }else if(*event == (std::string) "message"){
      V8INTERNAL(Wonderland::XmppConnection*)->signal_message.connect(
        boost::bind(&onMessage, func, _1, _2)
      );
    }else if(*event == (std::string) "disconnect"){
      V8INTERNAL(Wonderland::XmppConnection*)->signal_disconnected.connect(
        boost::bind(&onDisconnect, func)
      );
    }else{
      return ThrowException(v8::String::New("Invalid event argument"));
    }
    
    return v8::Undefined();
  }

  V8CALLBACK(Connection,connect){ 
    V8INTERNAL(Wonderland::XmppConnection*)->connect();
    return v8::Undefined();
  }

  V8CALLBACK(Connection,createAccount){ 
    V8INTERNAL(Wonderland::XmppConnection*)->createAccount();
    return v8::Undefined();
  }

  V8CALLBACK(Connection,send){ 
    if(args.Length()!=2)
      return ThrowException(v8::String::New("Invalid argument"));

    v8::String::Utf8Value user_id(args[0]);
    v8::String::Utf8Value message(args[1]);

    V8INTERNAL(Wonderland::XmppConnection*)->send(*user_id, *message);
    
    return v8::Undefined();
  }

  V8_GETTER(Connection, nickname){
    return v8::String::New(V8INTERNAL(Wonderland::XmppConnection*)->getNickname().c_str());
  }
  
  V8_SETTER(Connection, nickname){
    v8::String::Utf8Value str(value);
    V8INTERNAL(Wonderland::XmppConnection*)->setNickname((std::string) *str);
  }

  V8_GETTER(Connection, password){
    return v8::String::New(V8INTERNAL(Wonderland::XmppConnection*)->getPassword().c_str());
  }
  
  V8_SETTER(Connection, password){
    v8::String::Utf8Value str(value);
    V8INTERNAL(Wonderland::XmppConnection*)->setPassword((std::string) *str);
  }

  V8_GETTER(Connection, server){
    return v8::String::New(V8INTERNAL(Wonderland::XmppConnection*)->getServer().c_str());
  }
  
  V8_SETTER(Connection, server){
    v8::String::Utf8Value str(value);
    V8INTERNAL(Wonderland::XmppConnection*)->setServer((std::string) *str);
  }

  /* Construct a new instance */
  V8CALLBACK(Connection,create){
    v8::Persistent<v8::Object> self = v8::Persistent<v8::Object>::New(args.Holder());
    return self;
  }

  /* Sets internal fields with our idiom of storing the class name in the second field */
  void setInternalFields(v8::Persistent<v8::Object> p, Wonderland::XmppConnection *c){
    p->SetInternalField(0, v8::External::New(c));
    p->SetInternalField(1, v8::String::New("Connection"));
  }

  /* Only called from c++ */
  v8::Handle<v8::Value> New(Wonderland::XmppConnection *c){
    context_->Enter();

    v8::Persistent<v8::Object> instance = v8::Persistent<v8::Object>::New(function_template->GetFunction()->NewInstance());
    setInternalFields(instance, c);
    return instance;
  }

  void Initialize(Wonderland::ScriptingContext *sc){
    context_ = sc->getContext();

    // Function Template
    function_template = v8::FunctionTemplate::New(Connection_create);
    function_template->SetClassName(v8::String::New("Connection"));

    // Instance template
    instance_template = function_template->InstanceTemplate();
    instance_template->SetInternalFieldCount(2);

    instance_template->SetAccessor(v8::String::New("nickname"), Connection_getnickname, Connection_setnickname);
    instance_template->SetAccessor(v8::String::New("password"), Connection_getpassword, Connection_setpassword);
    instance_template->SetAccessor(v8::String::New("server"), Connection_getserver, Connection_setserver);

    #define V8REGISTER(function) instance_template->Set(#function, v8::FunctionTemplate::New(Connection_ ## function));
    
    V8REGISTER(_connectEvent);
    V8REGISTER(send);
    V8REGISTER(connect);
    V8REGISTER(createAccount);
  }

}